package jmine.tec.web.wicket.cronExpression;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import jmine.tec.utils.date.MonthEnum;
import jmine.tec.utils.date.TimeUnitEnum;
import jmine.tec.utils.date.WeekDaysEnum;

import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.markup.html.form.ListMultipleChoice;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.validation.validator.RangeValidator;

/**
 * Strategy to create cron expressions of PERIODIC. This strategy allows schedules with interval, with a periodicity, allows the user to
 * choose the days, months and week days that will trigger the cron expression. These last three options are optional, and if not selected,
 * will be considered as '*', '?', except the week days, that will be 'MON-FRI'.
 * 
 * @author Fabio Sakiyama
 * @created 24/11/2011
 */
public class PeriodicStrategy extends AbstractCronExpressionTypeStrategy {

    /**
     * C'tor
     * 
     * @param id wicket id
     */
    public PeriodicStrategy(String id) {
        super(id);
    }

    private Integer textFieldPeriodicity;

    private String selectInitialHour;

    private String selectEndHour;

    private TimeUnitEnum selectPeriodicityType;

    private List<String> daysList = new ArrayList<String>();

    private List<MonthEnum> monthsList = new ArrayList<MonthEnum>();

    private List<WeekDaysEnum> weekDaysList = new ArrayList<WeekDaysEnum>();

    /**
     * {@inheritDoc}
     */
    @Override
    public String doBuildCronExpression() {
        StringBuffer cron = new StringBuffer();
        cron.append("0 "); // Seconds

        if (this.selectPeriodicityType.equals(TimeUnitEnum.MINUTES)) {
            cron.append(this.formatMinutePeriodicity(this.textFieldPeriodicity));
            cron.append(this.formatInterval(this.selectInitialHour, this.selectEndHour));
        } else {
            cron.append("0 "); // Minutes
            cron.append(this.formatHourPeriodicity(this.selectInitialHour, this.selectEndHour, this.textFieldPeriodicity));
        }
        cron.append(this.formatDaysMonthsWeekDays(this.daysList, this.monthsList, this.weekDaysList));
        cron.append("*"); // Year
        return cron.toString();

    }

    /**
     * Receives the initial time, the end time and the periodicity, and formats it to occur in the given interval/periodicity.
     * 
     * @param initialHour initial hour of the interval
     * @param endHour end hour of the interval
     * @param periodicity periodicity (hour)
     * @return formatted interval and periodicity of the cron expression.
     */
    private String formatHourPeriodicity(String initialHour, String endHour, Integer periodicity) {
        return initialHour.concat("-" + endHour).concat("/" + periodicity + " ");
    }

    /**
     * Receives a periodicity, and formats it to occur in the given interval/periodicity.
     * 
     * @param periodicity periodicity (minute)
     * @return formatted interval and periodicity of the cron expression.
     */
    private String formatMinutePeriodicity(Integer periodicity) {
        return "*/".concat(periodicity.toString()) + " ";
    }

    /**
     * {@inheritDoc}
     */
    public Panel buildScreenFields(IModel<String> iModel, OptionalComponentsEnum... excludeFields) {
        this.setiModel(iModel);

        WebMarkupContainer containerDays = new WebMarkupContainer("containerDays");
        WebMarkupContainer containerMonths = new WebMarkupContainer("containerMonths");
        WebMarkupContainer containerWeekDays = new WebMarkupContainer("containerWeekDays");

        this.setOutputMarkupId(true);
        this.setOutputMarkupPlaceholderTag(true);

        // XXX fabio.sakiyama internacionalizar..
        Label labelDay = new Label("labelDay", "Dias: ");
        Label labelMonth = new Label("labelMonth", "Meses: ");
        Label labelWeekDay = new Label("labelWeekDay", "Dias da Semana: ");

        final DropDownChoice<String> initialHour = this.createHourDropDownChoice("selectInitialHour");
        final DropDownChoice<String> endHour = this.createHourDropDownChoice("selectEndHour");
        final DropDownChoice<TimeUnitEnum> periodicityType = this.createPeriodicityTypes("selectPeriodicityType");
        final TextField<Integer> periodicity = this.createPeriodicityTextField("textFieldPeriodicity");
        periodicityType.add(new AjaxFormComponentUpdatingBehavior("onchange") {
            @Override
            protected void onUpdate(AjaxRequestTarget target) {
                if (periodicityType.equals(TimeUnitEnum.HOURS)) {
                    periodicity.add(new RangeValidator<Integer>(MIN_HOUR, MAX_HOUR));
                } else {
                    periodicity.add(new RangeValidator<Integer>(MIN_MINUTES, MAX_MINUTES));
                }
            }
        });

        ListMultipleChoice<String> daysListMultipleChoice = this.createDaysListMultipleChoice("multipleListDays");
        ListMultipleChoice<MonthEnum> monthsListMultipleChoice = this.createMonthsListMultipleChoice("multipleListMonths");
        ListMultipleChoice<WeekDaysEnum> weekDaysListMultipleChoice = this.createWeekDaysListMultipleChoice("multipleListWeekDays");

        initialHour.setRequired(true);
        endHour.setRequired(true);
        periodicityType.setRequired(true);
        periodicity.setRequired(true);

        containerDays.add(labelDay);
        containerDays.add(daysListMultipleChoice);
        containerMonths.add(labelMonth);
        containerMonths.add(monthsListMultipleChoice);
        containerWeekDays.add(labelWeekDay);
        containerWeekDays.add(weekDaysListMultipleChoice);

        this.addOrReplace(containerDays);
        this.addOrReplace(containerMonths);
        this.addOrReplace(containerWeekDays);
        this.addOrReplace(periodicity);
        this.addOrReplace(initialHour);
        this.addOrReplace(endHour);
        this.addOrReplace(periodicityType);

        if (excludeFields != null) {
            for (OptionalComponentsEnum comp : excludeFields) {
                if (comp.isDays()) {
                    containerDays.setVisible(false);
                } else if (comp.isMonths()) {
                    containerMonths.setVisible(false);
                } else if (comp.isWeekDays()) {
                    containerWeekDays.setVisible(false);
                }
            }
        }
        return this;
    }

    /**
     * {@inheritDoc}
     */
    public void populateScreenFields(String cronExpression) {
        this.daysList.clear();
        this.monthsList.clear();
        this.weekDaysList.clear();

        String[] splittedCronExpression = cronExpression.split(" ");

        if (splittedCronExpression[1].equals("0")) {
            // If the periodicity type is HOURS, the format will be 'initialHour-endHour/Periodicity'
            String[] selectedHour = splittedCronExpression[HOUR_POSITION].split("-|/");
            this.selectInitialHour = selectedHour[0];
            this.selectEndHour = selectedHour[1];
            this.textFieldPeriodicity = Integer.parseInt(selectedHour[2]);
            this.selectPeriodicityType = TimeUnitEnum.HOURS;
        } else {
            // If the periodicity type is MINUTES, the format will be '*/periodicity initialHour-endHour'
            String[] selectedMinute = splittedCronExpression[MINUTE_POSITION].split("/");
            String[] selectedHour = splittedCronExpression[HOUR_POSITION].split("-");
            this.selectInitialHour = selectedHour[0];
            this.selectEndHour = selectedHour[1];
            this.textFieldPeriodicity = Integer.parseInt(selectedMinute[1]);
            this.selectPeriodicityType = TimeUnitEnum.MINUTES;
        }

        if (splittedCronExpression[DAYS_POSITION].equals("?") || splittedCronExpression[DAYS_POSITION].equals("*")) {
            this.daysList.addAll(this.maxDaysMonth());
        } else {
            String[] selectedDays = splittedCronExpression[DAYS_POSITION].split(",");
            for (String day : selectedDays) {
                this.daysList.add(day);
            }
        }

        if (splittedCronExpression[MONTHS_POSITION].equals("*")) {
            this.monthsList.addAll(Arrays.asList(MonthEnum.values()));
        } else {
            String[] selectedMonths = splittedCronExpression[MONTHS_POSITION].split(",");
            for (String month : selectedMonths) {
                this.monthsList.add(MonthEnum.valueOf(month));
            }
        }

        if (splittedCronExpression[WEEK_DAYS_POSITION].equals("?") || splittedCronExpression[WEEK_DAYS_POSITION].equals("*")) {
            this.weekDaysList.addAll(Arrays.asList(WeekDaysEnum.values()));
        } else {
            String[] selectedWeekDays = splittedCronExpression[WEEK_DAYS_POSITION].split(",");
            for (String weekDay : selectedWeekDays) {
                this.weekDaysList.add(this.getWeekDayNameByWeekDayInitial(weekDay));
            }
        }

    }

    /**
     * {@inheritDoc}
     */
    public boolean nullCheckAllFields() {
        if (this.textFieldPeriodicity == null || this.selectInitialHour == null || this.selectEndHour == null
                || this.selectPeriodicityType == null || this.daysList == null || this.monthsList == null || this.weekDaysList == null) {
            return false;
        }
        return true;
    }

    /**
     * @return the textFieldPeriodicity
     */
    public Integer getTextFieldPeriodicity() {
        return this.textFieldPeriodicity;
    }

    /**
     * @param textFieldPeriodicity the textFieldPeriodicity to set
     */
    public void setTextFieldPeriodicity(Integer textFieldPeriodicity) {
        this.textFieldPeriodicity = textFieldPeriodicity;
        this.buildCronExpression();
    }

    /**
     * @return the selectInitialHour
     */
    public String getSelectInitialHour() {
        return this.selectInitialHour;
    }

    /**
     * @param selectInitialHour the selectInitialHour to set
     */
    public void setSelectInitialHour(String selectInitialHour) {
        this.selectInitialHour = selectInitialHour;
        this.buildCronExpression();
    }

    /**
     * @return the selectEndHour
     */
    public String getSelectEndHour() {
        return this.selectEndHour;
    }

    /**
     * @param selectEndHour the selectEndHour to set
     */
    public void setSelectEndHour(String selectEndHour) {
        this.selectEndHour = selectEndHour;
        this.buildCronExpression();
    }

    /**
     * @return the selectPeriodicityType
     */
    public TimeUnitEnum getSelectPeriodicityType() {
        return this.selectPeriodicityType;
    }

    /**
     * @param selectPeriodicityType the selectPeriodicityType to set
     */
    public void setSelectPeriodicityType(TimeUnitEnum selectPeriodicityType) {
        this.selectPeriodicityType = selectPeriodicityType;
        this.buildCronExpression();
    }

    /**
     * @return the daysList
     */
    public List<String> getDaysList() {
        return this.daysList;
    }

    /**
     * @param daysList the daysList to set
     */
    public void setDaysList(List<String> daysList) {
        this.daysList = daysList;
        this.buildCronExpression();
    }

    /**
     * @return the monthsList
     */
    public List<MonthEnum> getMonthsList() {
        return this.monthsList;
    }

    /**
     * @param monthsList the monthsList to set
     */
    public void setMonthsList(List<MonthEnum> monthsList) {
        this.monthsList = monthsList;
        this.buildCronExpression();
    }

    /**
     * @return the weekDaysList
     */
    public List<WeekDaysEnum> getWeekDaysList() {
        return this.weekDaysList;
    }

    /**
     * @param weekDaysList the weekDaysList to set
     */
    public void setWeekDaysList(List<WeekDaysEnum> weekDaysList) {
        this.weekDaysList = weekDaysList;
        this.buildCronExpression();
    }
}
